# coding: utf-8

# Copyright (c) P SOFTHOUSE. All rights reserved.

import time

import BezierCurve.CurveEditor as CurveEditor
import maya.cmds as cmds  # pylint: disable=E0401
import maya.mel as mel  # pylint: disable=E0401
import maya.OpenMayaUI as omui# pylint: disable=E0401
import maya.utils as utils  # pylint: disable=E0401
# from PencilLineGuiUtils import *
# Using Qt.py for backward compatibility with PySide
# (used by Maya 2016.5 and older).
from PSOFT.vendor.Qt_py.Qt import QtCore, QtGui, QtWidgets

mayaApi = cmds.about(api=True)
if mayaApi >= 201700:
    from shiboken2 import wrapInstance, getCppPointer  # pylint: disable=E0401
else:
    from shiboken import wrapInstance, getCppPointer  # pylint: disable=E0401


# ------------------------------------------------------------------
# Class to display a graph interface that supports Bezier curves
class PencilBezierGraph(QtWidgets.QWidget):
    # Parent UI: Widget
    # Stores all instances of graphs indexed by their parent UI
    _bezierGraphs = dict()

    def __init__(self, idString, mayaLayoutName, parentWidget=None):
        super(PencilBezierGraph, self).__init__(parentWidget)
        self.curveEditor = CurveEditor.CurveWidget(parent=self)
        self.curveEditor.setMayaCallback(
            cpCreated=self.controlPointCreated,
            cpChanged=self.controlPointChanged,
            cpDestroyed=self.controlPointDestroyed,
            onCopy=PencilBezierGraph.onCopyDataAvailable,
            onPaste=self.setAttributeValues)
        self.layout = QtWidgets.QHBoxLayout(self)
        self.setLayout(self.layout)
        self.layout.addWidget(self.curveEditor)

        self.idString = idString
        self.mayaLayoutName = mayaLayoutName
        self.editingNodeName = ""
        self.editingAttributeName = ""

        # Register ourself in the list of Bezier graphs
        PencilBezierGraph._bezierGraphs[idString] = self

        self.lastCalledTime = time.clock()

    def __del__(self):
        # Remove ourself from the list of graph instances.
        del PencilBezierGraph._bezierGraphs[self.idString]

    # Rounds control point values to a fixed precision so we can ignore
    # minuscule changes in coordinates.
    @classmethod
    def roundValues(cls, values):
        # We limit the precision of point coordinates because otherwise
        # creating a new point is always followed by moving that point.
        POINT_COORDINATE_PRECISION = 7 # Number of decimal digits

        ret = [values[0]]
        for i in range(1, 7):
            ret.append(round(values[i], POINT_COORDINATE_PRECISION))

        return ret

    # Rounds the control point values to the supported precision and sets
    # them to the node attribute at the specified index.
    def setControlPoint(self, values, index):
        attributeIndexPath = "{0}.{1}[{2}]".format(self.editingNodeName,
                                                   self.editingAttributeName,
                                                   index)

        # Limit the precision of the coordinates
        myVals = PencilBezierGraph.roundValues(values)

        cmds.setAttr(attributeIndexPath, *myVals)

    def controlPointCreated(self, controlPoint):

        if hasattr(controlPoint, "__iter__"):
            cmds.undoInfo(openChunk=True)
            try:
                for e in controlPoint:
                    self.controlPointCreated(e)
            finally:
                cmds.undoInfo(closeChunk=True)
            return

        availableIndices = cmds.getAttr(
            self.editingNodeName + "." + self.editingAttributeName,
            mi=True)
        if availableIndices:
            controlPoint.mayaIndex = (max(availableIndices) + 1)
        else:
            controlPoint.mayaIndex = 1

        self.setControlPoint(controlPoint.getParams(), controlPoint.mayaIndex)

    def controlPointChanged(self, controlPoint):

        if hasattr(controlPoint, "__iter__"):
            cmds.undoInfo(openChunk=True)
            try:
                for e in controlPoint:
                    self.controlPointChanged(e)
            finally:
                cmds.undoInfo(closeChunk=True)
            return

        attributeIndexPath = "{0}.{1}[{2}]".format(self.editingNodeName,
                                                   self.editingAttributeName,
                                                   controlPoint.mayaIndex)

        # Limit the precision on the coordinates
        params = PencilBezierGraph.roundValues(controlPoint.getParams())

        attrVals = cmds.getAttr(attributeIndexPath)
        attrVals = PencilBezierGraph.roundValues(attrVals[0])

        # Only set the attribute if there was a change after limiting the precision.
        # This avoids registering 2 undo events for control point creation and
        # control point change, because of excessive precision, when we only
        # add a point.
        for i in range(0, len(params)):
            if attrVals[i] != params[i]:
                self.setControlPoint(params, controlPoint.mayaIndex)
                break

    def controlPointDestroyed(self, controlPoint):

        if hasattr(controlPoint, "__iter__"):
            cmds.undoInfo(openChunk=True)
            try:
                for e in controlPoint:
                    self.controlPointDestroyed(e)
            finally:
                cmds.undoInfo(closeChunk=True)
            return

        attributeIndexPath = "{0}.{1}[{2}]".format(self.editingNodeName,
                                                   self.editingAttributeName,
                                                   controlPoint.mayaIndex)
        cmds.removeMultiInstance(attributeIndexPath, b=True)

    # Re-initialize the managed attribute using the control point values provided.
    def setAttributeValues(self, controlPoints):
        attr = self.editingNodeName + '.' + self.editingAttributeName
        cmds.undoInfo(openChunk=True)
        try:
            # Delete all existing control points
            usedIdx = cmds.getAttr(attr, mi=True)
            if usedIdx:
                for idx in usedIdx:
                    cmds.removeMultiInstance(attr + '[' + str(idx) + ']', b=True)

            # Add all the new control points
            for cp in controlPoints:
                params = cp.getParams()
                self.setControlPoint(params, cp.mayaIndex)

        finally:
            cmds.undoInfo(closeChunk=True)

    def buildControlPoints(self):
        attributePath = self.editingNodeName + "." + self.editingAttributeName
        availableIndices = cmds.getAttr(attributePath, mi=True)
        if not availableIndices:
            return

        cmds.undoInfo(openChunk=True)
        try:
            for mayaIndex in availableIndices:
                attributeIndexPath = "{0}[{1:d}]".format(attributePath, mayaIndex)
                # MEMO: TupleのListが返ってくる *[(,)] => (,)
                params = cmds.getAttr(attributeIndexPath)[0]
                self.curveEditor.addControlPoint(mayaIndex, params)
            self.curveEditor.curveView._scene.controlPoints.recalcSpline()
        finally:
            cmds.undoInfo(closeChunk=True)

    def updateDisplay(self, isNewAttribute):
        # Preserve the selection if we did not change the attribute
        selectedIdx = []
        if not isNewAttribute:
            selectedIdx = self.curveEditor.getSelectedMayaIndices()

        self.curveEditor.resetControlPoint()
        self.buildControlPoints()

        # Restore the selection
        if len(selectedIdx) > 0:
            self.curveEditor.setSelectedMayaIndices(selectedIdx)

    def setAttribute(self, nodeName, attributeName):
        isNew = nodeName != self.editingNodeName \
              or attributeName != self.editingAttributeName

        self.editingNodeName = nodeName
        self.editingAttributeName = attributeName

        self.updateDisplay(isNew)

        # Setup a scriptJob to update the UI when an attribute value changes
        # (to react to changes made outside the UI, for example by Undo/redo
        # or in cases where there are more than 1 instance of the UI
        # (Attribute Editor and Hypershade))
        cmds.scriptJob(parent=self.mayaLayoutName, replacePrevious=True,
                     killWithScene=True, allChildren=True, disregardIndex=True,
                     attributeChange=[nodeName + '.' + attributeName,
                                      lambda: self.updateDisplay(False)])

    @classmethod
    def updateAttribute(cls, nodeName, attributeName, idString):
        if idString not in cls._bezierGraphs:
            cmds.error('Could not find graph for id=' +
                     idString)
            return

        graph = cls._bezierGraphs[idString]
        utils.executeDeferred(graph.setAttribute, nodeName, attributeName)

    @classmethod
    def onCopyDataAvailable(cls):
        # Enable the paste buttons on all Bezier graphs
        for graph in cls._bezierGraphs.values():
            graph.curveEditor.enablePasteButton()

    @classmethod
    def updateUI(cls, enable, idString):
        if idString not in cls._bezierGraphs:
            cmds.error('Could not find graph for id=' +
                     idString)
            return

        graph = cls._bezierGraphs[idString]
        graph.setEnabled(enable)


# ------------------------------------------------------------------
# 以下、MELから呼ばれる事を想定している関数
# ------------------------------------------------------------------

def getLayout(layoutName):
    mayaLayoutPtr = omui.MQtUtil.findLayout(layoutName)
    if (mayaLayoutPtr is not None):
        return wrapInstance(long(mayaLayoutPtr), QtWidgets.QWidget)
    return None


def buildAEBezierGraph(layoutName, attributePath, idString):
    mayaLayout = getLayout(layoutName)
    if not mayaLayout:
        return
    nodeName, attributeName = attributePath.split(".", 1)
    mayaLayoutObj = mayaLayout.findChild(QtWidgets.QBoxLayout)
    widget = PencilBezierGraph(idString, layoutName, mayaLayout)
    widget.setAttribute(nodeName, attributeName)
    mayaLayoutObj.insertWidget(0, widget)


def updateAEBezierGraph(attributePath, idString):
    nodeName, attributeName = attributePath.split(".", 1)
    PencilBezierGraph.updateAttribute(nodeName, attributeName, idString)


def updateEnable(enable, idString):
    PencilBezierGraph.updateUI(enable, idString)
